#!/usr/bin/env node

import {
  curlLongOpts,
  curlShortOpts,
  parseArgs,
  buildRequest,
  parseCurlCommand,
  CCError,
  has,
} from "./util.js";
import type { LongOpts, ShortOpts, Request } from "./util.js";

import { _toAnsible } from "./generators/ansible.js";
import { _toDart } from "./generators/dart.js";
import { _toCFML } from "./generators/cfml.js";
import { _toElixir } from "./generators/elixir.js";
import { _toGo } from "./generators/go.js";
import { _toJava } from "./generators/java.js";
import { _toJavaScript } from "./generators/javascript/javascript.js";
import { _toJsonString } from "./generators/json.js";
import { _toMATLAB } from "./generators/matlab/matlab.js";
import { _toNode } from "./generators/javascript/node-fetch.js";
import { _toNodeRequest } from "./generators/javascript/node-request.js";
import { _toPhp } from "./generators/php/php.js";
import { _toPhpRequests } from "./generators/php/php-requests.js";
import { _toPython } from "./generators/python.js";
import { _toR } from "./generators/r.js";
import { _toRust } from "./generators/rust.js";
import { _toStrest } from "./generators/strest.js";

import fs from "fs";

// This line is generated by extract_curl_args.py. Do not modify it.
const VERSION = "4.0.0-alpha.10 (curl 7.82.0)";

// sets a default in case --language isn't passed
const defaultLanguage = "python";

// Maps options for --language to functions
// NOTE: make sure to update this when adding language support
const translate: { [key: string]: (request: Request) => string } = {
  ansible: _toAnsible,
  cfml: _toCFML,
  browser: _toJavaScript, // for backwards compatibility, undocumented
  dart: _toDart,
  elixir: _toElixir,
  go: _toGo,
  java: _toJava,
  javascript: _toJavaScript,
  json: _toJsonString,
  matlab: _toMATLAB,
  node: _toNode,
  "node-request": _toNodeRequest,
  php: _toPhp,
  "php-requests": _toPhpRequests,
  python: _toPython,
  r: _toR,
  rust: _toRust,
  strest: _toStrest,
};

const USAGE = `Usage: curlconverter [--language <language>] [-] [curl_options...]

language: the language to convert the curl command to. The choices are
  ansible
  cfml
  dart
  elixir
  go
  java
  javascript
  json
  matlab
  node
  node-request
  php
  php-requests
  python (the default)
  r
  rust
  strest

-: read curl command from stdin

curl_options: these should be passed exactly as they would be passed to curl.
  see 'curl --help' or 'curl --manual' for which options are allowed here`;

const curlConverterLongOpts: LongOpts = {
  language: { type: "string", name: "language" },
  stdin: { type: "bool", name: "stdin" },
};
const curlConverterShortOpts: ShortOpts = {
  // a single - (dash) tells curlconverter to read input from stdin
  "": "stdin",
};
const opts: [LongOpts, ShortOpts] = [
  { ...curlLongOpts, ...curlConverterLongOpts },
  { ...curlShortOpts, ...curlConverterShortOpts },
];

function exitWithError(error: unknown, verbose = false): never {
  let errMsg: Error | string | unknown = error;
  if (!verbose) {
    if (error instanceof CCError) {
      errMsg = "";
      for (const line of error.message.toString().split("\n")) {
        errMsg += "error: " + line + "\n";
      }
      errMsg = (errMsg as string).trimEnd();
    } else if (error instanceof Error) {
      // .toString() removes the traceback
      errMsg = error.toString();
    }
  }
  console.error(errMsg);
  process.exit(2); // curl exits with 2 so we do too
}

const argv = process.argv.slice(2);
let parsedArguments;
try {
  parsedArguments = parseArgs(argv, opts);
} catch (e) {
  exitWithError(e);
}
if (parsedArguments.help) {
  console.log(USAGE.trim());
  process.exit(0);
}
if (parsedArguments.version) {
  console.log("curlconverter " + VERSION);
  process.exit(0);
}

const argc = Object.keys(parsedArguments).length;
const language = parsedArguments.language || defaultLanguage;
const stdin = parsedArguments.stdin;
if (!has(translate, language)) {
  exitWithError(
    new CCError(
      "unexpected --language: " +
        JSON.stringify(language) +
        "\n" +
        "must be one of: " +
        Object.keys(translate).join(", ")
    ),
    parsedArguments.verbose
  );
}
for (const opt of Object.keys(curlConverterLongOpts)) {
  delete parsedArguments[opt];
}

let request;
if (argc === 0) {
  console.log(USAGE.trim());
  process.exit(2);
} else if (stdin) {
  // This lets you do something like
  // echo curl example.com | curlconverter --verbose
  const extraArgs = Object.keys(parsedArguments).filter((a) => a !== "verbose");
  if (extraArgs.length > 0) {
    // Throw an error so that if user typos something like
    // curlconverter - -data
    // they aren't stuck with what looks like a hung terminal.
    const extraArgsStr = extraArgs.map((a) => "--" + a).join(", ");
    exitWithError(
      new CCError(
        "if you pass --stdin or -, you can't also pass " + extraArgsStr
      ),
      parsedArguments.verbose
    );
  }
  const input = fs.readFileSync(0, "utf8");
  try {
    request = parseCurlCommand(input);
  } catch (e) {
    exitWithError(e, parsedArguments.verbose);
  }
} else {
  try {
    request = buildRequest(parsedArguments);
  } catch (e) {
    exitWithError(e, parsedArguments.verbose);
  }
}

// Warning for users using the pre-4.0 CLI
if (request.url?.startsWith("curl ")) {
  console.error("warning: Passing a whole curl command as a single argument?");
  console.error(
    "warning: Pass options to curlconverter as if it was curl instead:"
  );
  console.error(
    "warning: curlconverter 'curl example.com' -> curlconverter example.com"
  );
}

const generator = translate[language];
let code;
try {
  code = generator(request);
} catch (e) {
  exitWithError(e, parsedArguments.verbose);
}
process.stdout.write(code);
